/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_render.cpp - Renders back-end systems
//


#include "r_local.h"


/*
 ==============================================================================

    CAPTURE RENDERER TO TEXTURE

 ==============================================================================
*/


/*
 ==================
 RB_CaptureCurrentRender

 Captures the frame buffer to a texture
 ==================
*/
static void RB_CaptureCurrentRender (int unit, texture_t *texture)
{
	int		w, h;

	if (r_skipCopyToTextures->integerValue)
		return;

	rg.pc.captureRenders++;

	// Find the texture dimensions
	if (glConfig.textureNonPowerOfTwo)
	{
		w = backEnd.renderWidth;
		h = backEnd.renderHeight;
	}
	else
	{
		w = NearestPowerOfTwo(backEnd.renderWidth, false);
		h = NearestPowerOfTwo(backEnd.renderHeight, false);
	}

	// Update the texture
	if (w == rg.currentRenderTexture->uploadWidth && h == rg.currentRenderTexture->uploadHeight)
	{
		rg.pc.captureRenderPixels += backEnd.renderWidth * backEnd.renderHeight;

		GL_SelectTexture(unit);
		GL_BindTexture(texture);

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, backEnd.renderWidth, backEnd.renderHeight);
	}
	else
	{
		rg.pc.captureRenderPixels += w * h;

		// Check the dimensions
		if (w > glConfig.maxTextureSize || h > glConfig.maxTextureSize)
			Com_Error(ERR_DROP, "RB_CaptureCurrentRender: size exceeds hardware limits (%i > %i or %i > %i)", w, glConfig.maxTextureSize, h, glConfig.maxTextureSize);

		// Reallocate the texture
		rg.currentRenderTexture->uploadWidth = w;
		rg.currentRenderTexture->uploadHeight = h;

		if (rg.currentRenderTexture->sourceSamples >= 3)
			rg.currentRenderTexture->uploadSize = w * h * 4;
		else
			rg.currentRenderTexture->uploadSize = w * h * rg.currentRenderTexture->sourceSamples;

		GL_SelectTexture(unit);
		GL_BindTexture(texture);

		qglCopyTexImage2D(GL_TEXTURE_2D, 0, rg.currentRenderTexture->uploadFormat, 0, 0, w, h, 0);
	}

	// Avoid bilerp issues
	if (w != backEnd.renderWidth)
	{
		rg.pc.captureRenderPixels += backEnd.renderHeight;

		GL_SelectTexture(unit);
		GL_BindTexture(texture);

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, backEnd.renderWidth, 0, backEnd.renderWidth - 1, 0, 1, backEnd.renderHeight);
	}

	if (h != glConfig.videoHeight)
	{
		rg.pc.captureRenderPixels += backEnd.renderWidth;

		GL_SelectTexture(unit);
		GL_BindTexture(texture);

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, backEnd.renderHeight, 0, backEnd.renderHeight - 1, backEnd.renderWidth, 1);
	}
}


/*
 ==============================================================================

    MESH DEFORMATION

 ==============================================================================
*/


/*
 ==================
 RB_DeformExpand
 ==================
*/
static void RB_DeformExpand ()
{
	vertexArray_t	*vertices = backEnd.vertices;
	float			expand;
	int				i;

	rg.pc.deforms++;
	rg.pc.deformVertices += backEnd.numVertices;

	expand = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[0]];

	for (i = 0; i < backEnd.numVertices; i++, vertices++)
		VectorMA(vertices->xyz, expand, vertices->normal, vertices->xyz);
}

/*
 ==================
 RB_DeformMove
 ==================
*/
static void RB_DeformMove ()
{
	vertexArray_t	*vertices = backEnd.vertices;
	vec3_t			move;
	int				i;

	rg.pc.deforms++;
	rg.pc.deformVertices += backEnd.numVertices;

	move[0] = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[0]];
	move[1] = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[1]];
	move[2] = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[2]];

	for (i = 0; i < backEnd.numVertices; i++, vertices++)
		VectorAdd(vertices->xyz, move, vertices->xyz);
}

/*
 ==================
 RB_DeformSprite
 ==================
*/
static void RB_DeformSprite ()
{
	vertexArray_t	*vertices = backEnd.vertices;
	vec3_t			viewMatrix[3];
	vec3_t			axis[3], matrix[3];
	vec3_t			center, tmp;
	int				i, j;

	if (backEnd.numIndices % 6)
	{
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform sprite' with an odd index count\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices % 4)
	{
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform sprite' with an odd vertex count\n", backEnd.material->name);
		return;
	}

	rg.pc.deforms++;
	rg.pc.deformVertices += backEnd.numVertices;

	VectorCopy(&backEnd.localParms.viewMatrix[0], viewMatrix[0]);
	VectorCopy(&backEnd.localParms.viewMatrix[4], viewMatrix[1]);
	VectorCopy(&backEnd.localParms.viewMatrix[8], viewMatrix[2]);

	for (i = 0; i < backEnd.numVertices; i += 4, vertices += 4)
	{
		center[0] = (vertices[0].xyz[0] + vertices[1].xyz[0] + vertices[2].xyz[0] + vertices[3].xyz[0]) * 0.25f;
		center[1] = (vertices[0].xyz[1] + vertices[1].xyz[1] + vertices[2].xyz[1] + vertices[3].xyz[1]) * 0.25f;
		center[2] = (vertices[0].xyz[2] + vertices[1].xyz[2] + vertices[2].xyz[2] + vertices[3].xyz[2]) * 0.25f;

		VectorSubtract(vertices[0].xyz, vertices[1].xyz, axis[0]);
		VectorSubtract(vertices[2].xyz, vertices[1].xyz, axis[1]);
		CrossProduct(axis[0], axis[1], axis[2]);
		VectorNormalizeFast(axis[2]);

		MakeNormalVectors(axis[2], axis[1], axis[0]);

		MatrixMultiply(viewMatrix, axis, matrix);

		for (j = 0; j < 4; j++)
		{
			VectorSubtract(vertices[j].xyz, center, tmp);
			VectorRotate(tmp, matrix, vertices[j].xyz);
			VectorAdd(vertices[j].xyz, center, vertices[j].xyz);
		}
	}
}

/*
 ==================
 RB_DeformTube
 ==================
*/
static void RB_DeformTube ()
{
	vertexArray_t	*vertices = backEnd.vertices;
	vec3_t			matrix[3];
	vec3_t			axis[3], invAxis[3];
	vec3_t			edge[3], length;
	vec3_t			normal, dir;
	vec3_t			center, tmp;
	int				shortAxis, longAxis;
	int				i, j;

	if (backEnd.numIndices % 6)
	{
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform tube' with an odd index count\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices % 4)
	{
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform tube' with an odd vertex count\n", backEnd.material->name);
		return;
	}

	rg.pc.deforms++;
	rg.pc.deformVertices += backEnd.numVertices;

	for (i = 0; i < backEnd.numVertices; i += 4, vertices += 4)
	{
		center[0] = (vertices[0].xyz[0] + vertices[1].xyz[0] + vertices[2].xyz[0] + vertices[3].xyz[0]) * 0.25f;
		center[1] = (vertices[0].xyz[1] + vertices[1].xyz[1] + vertices[2].xyz[1] + vertices[3].xyz[1]) * 0.25f;
		center[2] = (vertices[0].xyz[2] + vertices[1].xyz[2] + vertices[2].xyz[2] + vertices[3].xyz[2]) * 0.25f;

		if (backEnd.entity == rg.worldEntity)
			VectorSubtract(backEnd.viewParms.origin, center, dir);
		else
		{
			VectorAdd(center, backEnd.entity->origin, dir);
			VectorSubtract(backEnd.viewParms.origin, dir, tmp);
			VectorRotate(tmp, backEnd.entity->axis, dir);
		}

		VectorSubtract(vertices[1].xyz, vertices[0].xyz, edge[0]);
		VectorSubtract(vertices[2].xyz, vertices[0].xyz, edge[1]);
		VectorSubtract(vertices[2].xyz, vertices[1].xyz, edge[2]);

		length[0] = DotProduct(edge[0], edge[0]);
		length[1] = DotProduct(edge[1], edge[1]);
		length[2] = DotProduct(edge[2], edge[2]);

		if (length[2] > length[1] && length[2] > length[0])
		{
			if (length[1] > length[0])
			{
				shortAxis = 0;
				longAxis = 1;
			}
			else
			{
				shortAxis = 1;
				longAxis = 0;
			}
		}
		else if (length[1] > length[2] && length[1] > length[0])
		{
			if (length[2] > length[0])
			{
				shortAxis = 0;
				longAxis = 2;
			}
			else
			{
				shortAxis = 2;
				longAxis = 0;
			}
		}
		else if (length[0] > length[1] && length[0] > length[2])
		{
			if (length[2] > length[1]){
				shortAxis = 1;
				longAxis = 2;
			}
			else
			{
				shortAxis = 2;
				longAxis = 1;
			}
		}
		else
		{
			shortAxis = 0;
			longAxis = 0;
		}

		VectorNormalize2(edge[longAxis], normal);

		if (DotProduct(edge[shortAxis], edge[longAxis]))
		{
			VectorCopy(normal, edge[1]);

			if (normal[0] || normal[1])
				MakeNormalVectors(edge[1], edge[0], edge[2]);
			else
				MakeNormalVectors(edge[1], edge[2], edge[0]);
		}
		else
		{
			VectorNormalize2(edge[shortAxis], edge[0]);

			VectorCopy(normal, edge[1]);
			CrossProduct(edge[0], edge[1], edge[2]);
		}

		VectorMA(dir, -DotProduct(dir, normal), normal, axis[2]);
		VectorNormalizeFast(axis[2]);
		VectorCopy(normal, axis[1]);
		CrossProduct(axis[1], axis[2], axis[0]);

		AxisTranspose(axis, invAxis);

		MatrixMultiply(invAxis, edge, matrix);

		for (j = 0; j < 4; j++)
		{
			VectorSubtract(vertices[j].xyz, center, tmp);
			VectorRotate(tmp, matrix, vertices[j].xyz);
			VectorAdd(vertices[j].xyz, center, vertices[j].xyz);
		}
	}
}

/*
 ==================
 RB_Deform
 ==================
*/
void RB_Deform ()
{
	switch (backEnd.material->deform){
	case DFRM_NONE:

		break;
	case DFRM_EXPAND:
		RB_DeformExpand();

		break;
	case DFRM_MOVE:
		RB_DeformMove();

		break;
	case DFRM_SPRITE:
		RB_DeformSprite();

		break;
	case DFRM_TUBE:
		RB_DeformTube();

		break;
	default:
		Com_Error(ERR_DROP, "RB_Deform: unknown deform in material '%s'", backEnd.material->name);
	}
}


/*
 ==============================================================================

    MATERIAL SETUP

 ==============================================================================
*/


/*
 ==================
 RB_MultiplyTextureMatrix
 ==================
*/
void RB_MultiplyTextureMatrix (material_t *material, textureStage_t *textureStage, mat4_t matrix)
{
	float	s, t, angle;
	int		i;

	Matrix4_Identity(matrix);

	for (i = 0; i < textureStage->numTexTransforms; i++)
	{
		switch (textureStage->texTransform[i]){
		case TT_TRANSLATE:
			s = material->expressionRegisters[textureStage->texTransformRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texTransformRegisters[i][1]];

			Matrix4_Translate(matrix, s, t, 0.0f);

			break;
		case TT_SCROLL:
			s = material->expressionRegisters[textureStage->texTransformRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texTransformRegisters[i][1]];

			Matrix4_Translate(matrix, s, t, 0.0f);

			break;
		case TT_SCALE:
			s = material->expressionRegisters[textureStage->texTransformRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texTransformRegisters[i][1]];

			Matrix4_Scale(matrix, s, t, 1.0f);

			break;
		case TT_CENTERSCALE:
			s = material->expressionRegisters[textureStage->texTransformRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texTransformRegisters[i][1]];

			Matrix4_Translate(matrix, 0.5f, 0.5f, 0.0f);
			Matrix4_Scale(matrix, s, t, 1.0f);
			Matrix4_Translate(matrix, -0.5f, -0.5f, 0.0f);

			break;
		case TT_SHEAR:
			s = material->expressionRegisters[textureStage->texTransformRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texTransformRegisters[i][1]];

			Matrix4_Translate(matrix, 0.5f, 0.5f, 0.0f);
			Matrix4_Shear(matrix, s, t, 1.0f);
			Matrix4_Translate(matrix, -0.5f, -0.5f, 0.0f);

			break;
		case TT_ROTATE:
			angle = material->expressionRegisters[textureStage->texTransformRegisters[i][0]];

			Matrix4_Translate(matrix, 0.5f, 0.5f, 0.0f);
			Matrix4_Rotate(matrix, angle, 0.0f, 0.0f, 1.0f);
			Matrix4_Translate(matrix, -0.5f, -0.5f, 0.0f);

			break;
		default:
			Com_Error(ERR_DROP, "RB_MultiplyTextureMatrix: unknown texTransform in material '%s'", material->name);
		}
	}
}

/*
 ==================
 RB_SetupTextureStage
 ==================
*/
static void RB_SetupTextureStage (textureStage_t *textureStage)
{
	mat4_t	textureMatrix;
	bool	identity;

	switch (textureStage->texGen){
	case TG_EXPLICIT:
		qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
		qglTexCoordPointer(2, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_ST);

		identity = true;

		break;
	case TG_VECTOR:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);

		GL_TexGen(GL_S, GL_OBJECT_LINEAR);
		GL_TexGen(GL_T, GL_OBJECT_LINEAR);

		qglTexGenfv(GL_S, GL_OBJECT_PLANE, textureStage->texGenVectors[0]);
		qglTexGenfv(GL_T, GL_OBJECT_PLANE, textureStage->texGenVectors[1]);

		identity = true;

		break;
	case TG_REFLECT:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_R);

		GL_TexGen(GL_S, GL_REFLECTION_MAP_ARB);
		GL_TexGen(GL_T, GL_REFLECTION_MAP_ARB);
		GL_TexGen(GL_R, GL_REFLECTION_MAP_ARB);

		qglEnableClientState(GL_NORMAL_ARRAY);
		qglNormalPointer(GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_NORMAL);

		Matrix4_Transpose(backEnd.viewParms.worldMatrix, textureMatrix);

		identity = false;

		break;
	case TG_NORMAL:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_R);

		GL_TexGen(GL_S, GL_NORMAL_MAP_ARB);
		GL_TexGen(GL_T, GL_NORMAL_MAP_ARB);
		GL_TexGen(GL_R, GL_NORMAL_MAP_ARB);

		qglEnableClientState(GL_NORMAL_ARRAY);
		qglNormalPointer(GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_NORMAL);

		Matrix4_Transpose(backEnd.localParms.viewMatrix, textureMatrix);

		identity = false;

		break;
	case TG_SKYBOX:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_R);

		GL_TexGen(GL_S, GL_OBJECT_LINEAR);
		GL_TexGen(GL_T, GL_OBJECT_LINEAR);
		GL_TexGen(GL_R, GL_OBJECT_LINEAR);

		qglTexGenfv(GL_S, GL_OBJECT_PLANE, &backEnd.viewParms.skyBoxMatrix[ 0]);
		qglTexGenfv(GL_T, GL_OBJECT_PLANE, &backEnd.viewParms.skyBoxMatrix[ 4]);
		qglTexGenfv(GL_R, GL_OBJECT_PLANE, &backEnd.viewParms.skyBoxMatrix[ 8]);

		identity = true;

		break;
	case TG_SCREEN:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_Q);

		GL_TexGen(GL_S, GL_OBJECT_LINEAR);
		GL_TexGen(GL_T, GL_OBJECT_LINEAR);
		GL_TexGen(GL_Q, GL_OBJECT_LINEAR);

		qglTexGenfv(GL_S, GL_OBJECT_PLANE, &backEnd.viewParms.mirrorMatrix[ 0]);
		qglTexGenfv(GL_T, GL_OBJECT_PLANE, &backEnd.viewParms.mirrorMatrix[ 4]);
		qglTexGenfv(GL_Q, GL_OBJECT_PLANE, &backEnd.viewParms.mirrorMatrix[12]);

		identity = true;

		break;
	default:
		Com_Error(ERR_DROP, "RB_SetupTextureStage: unknown texGen in material '%s'", backEnd.material->name);
	}

	if (identity)
	{
		if (!textureStage->numTexTransforms)
			GL_LoadIdentity(GL_TEXTURE);
		else
		{
			RB_MultiplyTextureMatrix(backEnd.material, textureStage, textureMatrix);

			GL_LoadMatrix(GL_TEXTURE, textureMatrix);
		}
	}
	else
		GL_LoadMatrix(GL_TEXTURE, textureMatrix);

	GL_EnableTexture(textureStage->texture->uploadTarget);
	GL_BindTexture(textureStage->texture);

	if (textureStage->texture == rg.currentRenderTexture)
	{
		if (backEnd.material->sort != SORT_POST_PROCESS)
			RB_CaptureCurrentRender(0, textureStage->texture);

		return;
	}
}

/*
 ==================
 RB_CleanupTextureStage
 ==================
*/
static void RB_CleanupTextureStage (textureStage_t *textureStage)
{
	if (textureStage->texGen == TG_EXPLICIT)
	{
		qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
		return;
	}

	if (textureStage->texGen == TG_REFLECT || textureStage->texGen == TG_NORMAL)
		qglDisableClientState(GL_NORMAL_ARRAY);

	GL_Disable(GL_TEXTURE_GEN_Q);
	GL_Disable(GL_TEXTURE_GEN_R);
	GL_Disable(GL_TEXTURE_GEN_T);
	GL_Disable(GL_TEXTURE_GEN_S);
}

/*
 ==================
 RB_SetupColorStage
 ==================
*/
static void RB_SetupColorStage (colorStage_t *colorStage)
{
	vec4_t	color;

	color[0] = backEnd.material->expressionRegisters[colorStage->registers[0]];
	color[1] = backEnd.material->expressionRegisters[colorStage->registers[1]];
	color[2] = backEnd.material->expressionRegisters[colorStage->registers[2]];
	color[3] = backEnd.material->expressionRegisters[colorStage->registers[3]];

	switch (colorStage->vertexColor){
	case VC_IGNORE:
		if (colorStage->identity)
		{
			qglColor3f(1.0f, 1.0f, 1.0f);

			GL_TexEnv(GL_REPLACE);
			break;
		}

		qglColor4fv(color);

		GL_TexEnv(GL_MODULATE);

		break;
	case VC_MODULATE:
		qglEnableClientState(GL_COLOR_ARRAY);
		qglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_COLOR);

		GL_TexEnv(GL_MODULATE);

		if (colorStage->identity)
			break;

		GL_SelectTexture(1);
		GL_EnableTexture(GL_TEXTURE_2D);
		GL_BindTexture(rg.whiteTexture);

		GL_TexEnv(GL_COMBINE_ARB);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_CONSTANT_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

		qglTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);

		GL_SelectTexture(0);

		break;
	case VC_INVERSE_MODULATE:
		qglEnableClientState(GL_COLOR_ARRAY);
		qglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_COLOR);

		GL_TexEnv(GL_COMBINE_ARB);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_ONE_MINUS_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_ONE_MINUS_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

		if (colorStage->identity)
			break;

		GL_SelectTexture(1);
		GL_EnableTexture(GL_TEXTURE_2D);
		GL_BindTexture(rg.whiteTexture);

		GL_TexEnv(GL_COMBINE_ARB);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_CONSTANT_ARB);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

		qglTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);

		GL_SelectTexture(0);

		break;
	default:
		Com_Error(ERR_DROP, "RB_SetupColorStage: unknown vertexColor in material '%s'", backEnd.material->name);
	}
}

/*
 ==================
 RB_CleanupColorStage
 ==================
*/
static void RB_CleanupColorStage (colorStage_t *colorStage)
{
	if (colorStage->vertexColor == VC_IGNORE)
		return;

	GL_SelectTexture(1);
	GL_DisableTexture();
	GL_SelectTexture(0);

	qglDisableClientState(GL_COLOR_ARRAY);
}

/*
 ==================
 RB_SetupShaderStage

 TODO: Fix UT_VIEW_AXIS
 ==================
*/
static void RB_SetupShaderStage (shaderStage_t *shaderStage)
{
	uniform_t	   *uniform;
	shaderParm_t   *shaderParm;
	shaderMap_t	   *shaderMap;
	int			   i;

	// Bind the program
	GL_BindProgram(shaderStage->program);

	// Set up the arrays
	qglEnableClientState(GL_NORMAL_ARRAY);
	qglNormalPointer(GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_NORMAL);

	qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
	qglTexCoordPointer(2, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_ST);

	qglEnableClientState(GL_COLOR_ARRAY);
	qglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_COLOR);

	qglEnableVertexAttribArrayARB(10);
	qglVertexAttribPointerARB(10, 3, GL_FLOAT, false, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_TANGENT0);

	qglEnableVertexAttribArrayARB(11);
	qglVertexAttribPointerARB(11, 3, GL_FLOAT, false, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_TANGENT1);

	// Set up the predefined uniforms
	for (i = 0, uniform = shaderStage->program->uniforms; i < shaderStage->program->numUniforms; i++, uniform++)
	{
		if (uniform->type == UT_CUSTOM)
			continue;

		switch (uniform->type){
		case UT_VIEW_ORIGIN:
			R_UniformVector3(uniform, backEnd.localParms.viewOrigin);
			break;
		case UT_VIEW_AXIS:
			R_UniformMatrix3(uniform, GL_TRUE, backEnd.localParms.viewAxis);
			break;
		case UT_ENTITY_ORIGIN:
			R_UniformVector3(uniform, backEnd.entity->origin);
			break;
		case UT_ENTITY_AXIS:
			R_UniformMatrix3(uniform, GL_TRUE, backEnd.entity->axis);
			break;
		case UT_COORD_SCALE_AND_BIAS:
			R_UniformFloat4(uniform, backEnd.renderScale[0], backEnd.renderScale[1], backEnd.renderBias[0], backEnd.renderBias[1]);
			break;
		case UT_COLOR_SCALE_AND_BIAS:
			R_UniformFloat2(uniform, shaderStage->colorScale, shaderStage->colorBias);
			break;
		default:
			Com_Error(ERR_DROP, "RB_SetupShaderStage: unknown shader uniform type in material '%s'", backEnd.material->name);
		}
	}

	// Set up the shader parms
	for (i = 0, shaderParm = shaderStage->shaderParms; i < shaderStage->numShaderParms; i++, shaderParm++)
	{
		switch (shaderParm->uniform->format){
		case GL_FLOAT:
			R_UniformFloat(shaderParm->uniform, backEnd.material->expressionRegisters[shaderParm->registers[0]]);
			break;
		case GL_FLOAT_VEC2:
			R_UniformFloat2(shaderParm->uniform, backEnd.material->expressionRegisters[shaderParm->registers[0]], backEnd.material->expressionRegisters[shaderParm->registers[1]]);
			break;
		case GL_FLOAT_VEC3:
			R_UniformFloat3(shaderParm->uniform, backEnd.material->expressionRegisters[shaderParm->registers[0]], backEnd.material->expressionRegisters[shaderParm->registers[1]], backEnd.material->expressionRegisters[shaderParm->registers[2]]);
			break;
		case GL_FLOAT_VEC4:
			R_UniformFloat4(shaderParm->uniform, backEnd.material->expressionRegisters[shaderParm->registers[0]], backEnd.material->expressionRegisters[shaderParm->registers[1]], backEnd.material->expressionRegisters[shaderParm->registers[2]], backEnd.material->expressionRegisters[shaderParm->registers[3]]);
			break;
		}
	}

	// Set up the shader maps
	for (i = 0, shaderMap = shaderStage->shaderMaps; i < shaderStage->numShaderMaps; i++, shaderMap++)
	{
		GL_BindProgramTexture(shaderMap->uniform->unit, shaderMap->texture);

		if (shaderMap->texture == rg.currentRenderTexture)
		{
			GL_SelectTexture(shaderMap->uniform->unit);
			GL_BindTexture(shaderMap->texture);

			if (backEnd.material->sort != SORT_POST_PROCESS)
				RB_CaptureCurrentRender(shaderMap->uniform->unit, shaderMap->texture);

			continue;
		}
	}
}

/*
 ==================
 RB_CleanupShaderStage
 ==================
*/
static void RB_CleanupShaderStage (shaderStage_t *shaderStage)
{
	GL_SelectTexture(0);

	GL_UnbindPrograms();

	qglDisableVertexAttribArrayARB(11);
	qglDisableVertexAttribArrayARB(10);

	qglDisableClientState(GL_COLOR_ARRAY);
	qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
	qglDisableClientState(GL_NORMAL_ARRAY);
}

/*
 ==================
 RB_SetMaterialState
 ==================
*/
static void RB_SetMaterialState ()
{
	if (backEnd.material->cullType == CT_FRONT_SIDED)
	{
		GL_Enable(GL_CULL_FACE);
		GL_CullFace(GL_FRONT);
	}
	else if (backEnd.material->cullType == CT_BACK_SIDED)
	{
		GL_Enable(GL_CULL_FACE);
		GL_CullFace(GL_BACK);
	}
	else
		GL_Disable(GL_CULL_FACE);

	if (backEnd.material->polygonOffset)
	{
		GL_Enable(GL_POLYGON_OFFSET_FILL);
		GL_PolygonOffset(r_offsetFactor->floatValue, r_offsetUnits->floatValue);
	}
	else
		GL_Disable(GL_POLYGON_OFFSET_FILL);
}

/*
 ==================
 RB_SetStageState
 ==================
*/
static void RB_SetStageState (stage_t *stage)
{
	if (backEnd.material->polygonOffset || stage->privatePolygonOffset)
	{
		GL_Enable(GL_POLYGON_OFFSET_FILL);
		GL_PolygonOffset(r_offsetFactor->floatValue, r_offsetUnits->floatValue);
	}
	else
		GL_Disable(GL_POLYGON_OFFSET_FILL);

	if (stage->alphaTest)
	{
		GL_Enable(GL_ALPHA_TEST);
		GL_AlphaFunc(GL_GREATER, backEnd.material->expressionRegisters[stage->alphaTestRegister]);
	}
	else
		GL_Disable(GL_ALPHA_TEST);

	if (stage->blend)
	{
		GL_Enable(GL_BLEND);
		GL_BlendFunc(stage->blendSrc, stage->blendDst);
	}
	else
		GL_Disable(GL_BLEND);

	if (stage->ignoreAlphaTest)
		GL_DepthFunc(GL_LEQUAL);
	else
		GL_DepthFunc(GL_EQUAL);

	GL_ColorMask(!stage->maskRed, !stage->maskGreen, !stage->maskBlue, !stage->maskAlpha);
}


/*
 ==============================================================================

    Z-BUFFER RENDERING PASS

 ==============================================================================
*/


/*
 ==================
 RB_RenderDepth
 ==================
*/
static void RB_RenderDepth ()
{
	stage_t	*stage;
	bool	hasAlphaTest = false;
	int		i;

	RB_Deform();

	RB_UpdateVertexBuffer(backEnd.vertexBuffer, backEnd.vertices, backEnd.numVertices * sizeof(vertexArray_t));

	RB_SetMaterialState();

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglVertexPointer(3, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_XYZ);

	if (backEnd.material->coverage == MC_PERFORATED)
	{
		for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++)
		{
			if (!backEnd.material->expressionRegisters[stage->conditionRegister])
				continue;

			if (stage->shaders)
				continue;

			if (!stage->alphaTest)
				continue;

			hasAlphaTest = true;

			if (backEnd.material->polygonOffset || stage->privatePolygonOffset)
			{
				GL_Enable(GL_POLYGON_OFFSET_FILL);
				GL_PolygonOffset(r_offsetFactor->floatValue, r_offsetUnits->floatValue);
			}
			else
				GL_Disable(GL_POLYGON_OFFSET_FILL);

			GL_Enable(GL_ALPHA_TEST);
			GL_AlphaFunc(GL_GREATER, backEnd.material->expressionRegisters[stage->alphaTestRegister]);

			GL_Disable(GL_BLEND);

			GL_TexEnv(GL_MODULATE);

			RB_SetupTextureStage(&stage->textureStage);

			RB_DrawElements();

			RB_CleanupTextureStage(&stage->textureStage);
		}

		if (hasAlphaTest)
			return;
	}

	GL_DisableTexture();

	GL_Disable(GL_ALPHA_TEST);

	if (backEnd.material->sort == SORT_SUBVIEW)
	{
		GL_Enable(GL_BLEND);
		GL_BlendFunc(GL_ZERO, GL_ONE);
	}
	else
		GL_Disable(GL_BLEND);

	RB_DrawElements();
}

/*
 ==================
 RB_FillDepthBuffer
 ==================
*/
void RB_FillDepthBuffer (mesh_t *meshes, int numMeshes)
{
	mesh_t			 *mesh;
	uint		     sort = 0;
	material_t		 *material, *oldMaterial = NULL;
	renderEntity_t   *entity, *oldEntity = NULL;
	meshType_t		 type;
	bool		     caps;
	bool		     skip;
	int				 i;

	if (!numMeshes)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_FillDepthBuffer ----------\n");

	// Set the render function
	backEnd.renderBatch = RB_RenderDepth;

	// Set the GL state
	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_ALWAYS, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_TRUE);
	GL_StencilMask(255);

	qglColor3f(0.0f, 0.0f, 0.0f);

	// Run through the meshes
	for (i = 0, mesh = meshes; i < numMeshes; i++, mesh++)
	{
		if (mesh->sort != sort)
		{
			sort = mesh->sort;

			// Render the last batch
			RB_RenderBatch();

			// Decompose sort
			R_DecomposeSort(sort, &material, &entity, &type, &caps);

			// Stop if it's translucent
			if (material->coverage == MC_TRANSLUCENT)
				break;

			// Evaluate registers if needed
			if (material != oldMaterial || entity != oldEntity)
				R_EvaluateRegisters(material, backEnd.time, entity->materialParms, backEnd.materialParms);

			// Skip if condition evaluated to false
			if (!material->expressionRegisters[material->conditionRegister])
			{
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (entity != oldEntity)
				RB_SetEntityState(entity);

			// Create a new batch
			RB_SetupBatch(material, entity, false, false);

			oldMaterial = material;
			oldEntity = entity;

			skip = false;
		}

		if (skip)
			continue;

		// Batch the mesh
		RB_BatchMesh(type, mesh->data);
	}

	// Render the last batch
	RB_RenderBatch();
}


/*
 ==============================================================================

    MATERIAL RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_RenderMaterial
 ==================
*/
static void RB_RenderMaterial ()
{
	stage_t		*stage;
	int			i;

	if (r_logFile->integerValue)
		QGL_LogPrintf("----- RB_RenderMaterial ( %s ) -----\n", backEnd.material->name);

	RB_Deform();

	RB_UpdateVertexBuffer(backEnd.vertexBuffer, backEnd.vertices, backEnd.numVertices * sizeof(vertexArray_t));

	RB_SetMaterialState();

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglVertexPointer(3, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_XYZ);

	for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++)
	{
		if (!backEnd.material->expressionRegisters[stage->conditionRegister])
			continue;

		if (stage->lighting != SL_AMBIENT)
			continue;

		if (stage->blend)
		{
			if (stage->blendSrc == GL_ZERO && stage->blendDst == GL_ONE)
				continue;
		}

		RB_SetStageState(stage);

		if (stage->shaders)
		{
			RB_SetupShaderStage(&stage->shaderStage);

			RB_DrawElements();

			RB_CleanupShaderStage(&stage->shaderStage);
		}
		else
		{
			RB_SetupTextureStage(&stage->textureStage);
			RB_SetupColorStage(&stage->colorStage);

			RB_DrawElements();

			RB_CleanupColorStage(&stage->colorStage);
			RB_CleanupTextureStage(&stage->textureStage);
		}
	}
}

/*
 ==================
 RB_RenderMaterialPasses
 ==================
*/
void RB_RenderMaterialPasses (mesh_t *meshes, int numMeshes, bool postProcess)
{
	mesh_t			 *mesh;
	uint		     sort = 0;
	material_t		 *material, *oldMaterial = NULL;
	renderEntity_t   *entity, *oldEntity = NULL;
	meshType_t		 type;
	bool		     caps;
	bool		     skip;
	int				 i;

	if (!numMeshes)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_RenderMaterialPasses ----------\n");

	// Capture current render if needed
	if (postProcess)
		RB_CaptureCurrentRender(0, rg.currentRenderTexture);

	// Set the render function
	backEnd.renderBatch = RB_RenderMaterial;

	// Set the GL state
	GL_Enable(GL_DEPTH_TEST);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_ALWAYS, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	if (postProcess)
		GL_DepthMask(GL_TRUE);
	else
		GL_DepthMask(GL_FALSE);

	GL_StencilMask(255);

	// Run through the meshes
	for (i = 0, mesh = meshes; i < numMeshes; i++, mesh++)
	{
		if (mesh->sort != sort)
		{
			sort = mesh->sort;

			// Render the last batch
			RB_RenderBatch();

			// Decompose sort
			R_DecomposeSort(sort, &material, &entity, &type, &caps);

			// Skip if it doesn't have ambient stages
			if (!material->numAmbientStages)
			{
				skip = true;
				continue;
			}

			// Evaluate registers if needed
			if (material != oldMaterial || entity != oldEntity)
				R_EvaluateRegisters(material, backEnd.time, entity->materialParms, backEnd.materialParms);

			// Skip if condition evaluated to false
			if (!material->expressionRegisters[material->conditionRegister])
			{
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (entity != oldEntity)
				RB_SetEntityState(entity);

			// Create a new batch
			RB_SetupBatch(material, entity, false, false);

			oldMaterial = material;
			oldEntity = entity;

			skip = false;
		}

		if (skip)
			continue;

		// Batch the mesh
		RB_BatchMesh(type, mesh->data);
	}

	// Render the last batch
	RB_RenderBatch();
}


/*
 ==============================================================================

    STENCIL SHADOW AND LIGHT INTERACTION RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_RenderStencilShadow
 ==================
*/
static void RB_RenderStencilShadow ()
{
	RB_UpdateVertexBuffer(backEnd.shadowVertexBuffer, backEnd.shadowVertices, backEnd.numVertices * sizeof(shadowVertexArray_t));

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglVertexPointer(4, GL_FLOAT, sizeof(shadowVertexArray_t), backEnd.shadowVertexBuffer->pointer + BUFFER_OFFSET_XYZW);

	if (backEnd.shadowCaps)
	{
		if (glConfig.stencilTwoSide)
		{
			qglActiveStencilFaceEXT(GL_BACK);
			GL_StencilOp(GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
			qglActiveStencilFaceEXT(GL_FRONT);
			GL_StencilOp(GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);

			qglEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);

			RB_DrawElements();

			qglDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);
		}
		else if (glConfig.atiSeparateStencil)
		{
			qglStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);
			qglStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);

			RB_DrawElements();

			qglStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP);
			qglStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP);
		}
		else
		{
			GL_CullFace(GL_BACK);
			GL_StencilOp(GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);

			RB_DrawElements();

			GL_CullFace(GL_FRONT);
			GL_StencilOp(GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);

			RB_DrawElements();
		}
	}
	else
	{
		if (glConfig.stencilTwoSide)
		{
			qglActiveStencilFaceEXT(GL_BACK);
			GL_StencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP_EXT);
			qglActiveStencilFaceEXT(GL_FRONT);
			GL_StencilOp(GL_KEEP, GL_KEEP, GL_DECR_WRAP_EXT);

			qglEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);

			RB_DrawElements();

			qglDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);
		}
		else if (glConfig.atiSeparateStencil)
		{
			qglStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_KEEP, GL_INCR_WRAP_EXT);
			qglStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_KEEP, GL_DECR_WRAP_EXT);

			RB_DrawElements();

			qglStencilOpSeparateATI(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP);
			qglStencilOpSeparateATI(GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP);
		}
		else
		{
			GL_CullFace(GL_FRONT);
			GL_StencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP_EXT);

			RB_DrawElements();

			GL_CullFace(GL_BACK);
			GL_StencilOp(GL_KEEP, GL_KEEP, GL_DECR_WRAP_EXT);

			RB_DrawElements();
		}
	}
}

/*
 ==================
 RB_StencilShadowPass
 ==================
*/
static void RB_StencilShadowPass (mesh_t *meshes, int numMeshes)
{
	mesh_t			 *mesh;
	uint		     sort = 0;
	material_t		 *material, *oldMaterial = NULL;
	renderEntity_t   *entity, *oldEntity = NULL;
	meshType_t		 type;
	bool			 caps;
	bool			 skip;
	int				 i;

	if (!numMeshes)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_StencilShadowPass ----------\n");

	// Set the render function
	backEnd.renderBatch = RB_RenderStencilShadow;

	// Set the GL state
	GL_DisableTexture();

	if (glConfig.stencilTwoSide || glConfig.atiSeparateStencil)
		GL_Disable(GL_CULL_FACE);
	else
		GL_Enable(GL_CULL_FACE);

	GL_Enable(GL_POLYGON_OFFSET_FILL);
	GL_PolygonOffset(r_shadowOffsetFactor->floatValue, r_shadowOffsetUnits->floatValue);

	GL_Disable(GL_ALPHA_TEST);

	GL_Disable(GL_BLEND);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_ALWAYS, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	GL_ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(255);

	// Run through the meshes
	for (i = 0, mesh = meshes; i < numMeshes; i++, mesh++)
	{
		if (mesh->sort != sort)
		{
			sort = mesh->sort;

			// Render the last batch
			RB_RenderBatch();

			// Decompose sort
			R_DecomposeSort(sort, &material, &entity, &type, &caps);

			// Evaluate registers if needed
			if (material != oldMaterial || entity != oldEntity)
				R_EvaluateRegisters(material, backEnd.time, entity->materialParms, backEnd.materialParms);

			// Skip if condition evaluated to false
			if (!material->expressionRegisters[material->conditionRegister])
			{
				skip = true;
				continue;
			}

			// Set the entity state if needed
			if (entity != oldEntity)
			{
				RB_SetEntityState(entity);

				// Set the light state for this entity
				RB_SetLightState(entity, backEnd.light, NULL, NULL);
			}

			// Create a new batch
			RB_SetupBatch(material, entity, true, caps);

			oldMaterial = material;
			oldEntity = entity;

			skip = false;
		}

		if (skip)
			continue;

		// Batch the mesh
		RB_BatchMeshShadow(type, mesh->data);
	}

	// Render the last batch
	RB_RenderBatch();
}

/*
 ==================
 RB_RenderInteractions
 ==================
*/
static void RB_RenderInteractions ()
{
	stage_t		*bumpStage = NULL, *diffuseStage = NULL, *specularStage = NULL;
	stage_t		*stage;
	int			i;

	if (r_logFile->integerValue)
		QGL_LogPrintf("----- RB_RenderInteractions ( %s on %s ) -----\n", backEnd.lightMaterial->name, backEnd.material->name);

	RB_Deform();

	RB_UpdateVertexBuffer(backEnd.vertexBuffer, backEnd.vertices, backEnd.numVertices * sizeof(vertexArray_t));

	RB_SetMaterialState();

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglVertexPointer(3, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_XYZ);

	for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++)
	{
		if (!backEnd.material->expressionRegisters[stage->conditionRegister])
			continue;

		if (stage->lighting == SL_AMBIENT)
			continue;

		switch (stage->lighting){
		case SL_BUMP:
			if (bumpStage)
				RB_RenderInteraction(bumpStage, diffuseStage, specularStage);
			else
			{
				if (diffuseStage || specularStage)
					Com_Printf(S_COLOR_YELLOW "Material '%s' has a bump stage with a preceeding diffuse/specular stage\n", backEnd.material->name);
			}

			bumpStage = stage;
			diffuseStage = NULL;
			specularStage = NULL;

			break;
		case SL_DIFFUSE:
			if (diffuseStage)
			{
				if (bumpStage)
					RB_RenderInteraction(bumpStage, diffuseStage, specularStage);
				else
					Com_Printf(S_COLOR_YELLOW "Material '%s' has a diffuse stage without a preceeding bump stage\n", backEnd.material->name);
			}

			diffuseStage = stage;

			break;
		case SL_SPECULAR:
			if (specularStage)
			{
				if (bumpStage)
					RB_RenderInteraction(bumpStage, diffuseStage, specularStage);
				else
					Com_Printf(S_COLOR_YELLOW "Material '%s' has a specular stage without a preceeding bump stage", backEnd.material->name);
			}

			specularStage = stage;

			break;
		}
	}

	RB_RenderInteraction(bumpStage, diffuseStage, specularStage);
}

/*
 ==================
 RB_LightInteractionPass
 ==================
*/
static void RB_LightInteractionPass (mesh_t *meshes, int numMeshes)
{
	mesh_t			 *mesh;
	uint			 sort = 0;
	material_t		 *material, *oldMaterial = NULL;
	renderEntity_t   *entity, *oldEntity = NULL;
	meshType_t		 type;
	bool			 caps;
	bool			 skip;
	int				 i, j;

	if (!numMeshes)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_LightInteractionPass ----------\n");

	// Set the render function
	backEnd.renderBatch = RB_RenderInteractions;

	// Set the GL state
	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_BLEND);
	GL_BlendFunc(GL_ONE, GL_ONE);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_EQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_GEQUAL, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(255);

	// Run through the light stages
	for (i = 0, backEnd.lightStage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, backEnd.lightStage++)
	{
		if (!backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->conditionRegister])
			continue;

		// Set the GL state
		if (backEnd.light->castShadows)
		{
			if (backEnd.lightStage->shadowDraw)
				GL_StencilFunc(GL_LESS, 128, 255);
			else
				GL_StencilFunc(GL_GEQUAL, 128, 255);
		}
		else
		{
			if (backEnd.lightStage->shadowDraw)
				continue;

			GL_StencilFunc(GL_ALWAYS, 128, 255);
		}

		// Run through the meshes
		for (j = 0, mesh = meshes; j < numMeshes; j++, mesh++)
		{
			if (mesh->sort != sort)
			{
				sort = mesh->sort;

				// Render the last batch
				RB_RenderBatch();

				// Decompose sort
				R_DecomposeSort(sort, &material, &entity, &type, &caps);

				// Evaluate registers if needed
				if (material != oldMaterial || entity != oldEntity)
					R_EvaluateRegisters(material, backEnd.time, entity->materialParms, backEnd.materialParms);

				// Skip if condition evaluated to false
				if (!material->expressionRegisters[material->conditionRegister])
				{
					skip = true;
					continue;
				}

				// Set the entity state if needed
				if (entity != oldEntity)
				{
					RB_SetEntityState(entity);

					// Set the light state for this entity
					RB_SetLightState(entity, backEnd.light, backEnd.lightMaterial, backEnd.lightStage);
				}

				// Create a new batch
				RB_SetupBatch(material, entity, false, false);

				oldMaterial = material;
				oldEntity = entity;

				skip = false;
			}

			if (skip)
				continue;

			// Batch the mesh
			RB_BatchMesh(type, mesh->data);
		}

		// Render the last batch
		RB_RenderBatch();
	}

	// Restore the GL state
	GL_UnbindPrograms();
}

/*
 ==================
 RB_RenderLights
 ==================
*/
void RB_RenderLights (light_t *lights, int numLights)
{
	int		i;

	if (!numLights)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_RenderLights ----------\n");

	// Run through the lights
	for (i = 0, backEnd.light = lights; i < numLights; i++, backEnd.light++)
	{
		// Set the light material
		backEnd.lightMaterial = backEnd.light->material;

		// Evaluate registers
		R_EvaluateRegisters(backEnd.lightMaterial, backEnd.time, backEnd.light->materialParms, backEnd.materialParms);

		// Skip if condition evaluated to false
		if (!backEnd.lightMaterial->expressionRegisters[backEnd.lightMaterial->conditionRegister])
			continue;

		// Set the scissor rect
		GL_Scissor(backEnd.light->scissorX, backEnd.light->scissorY, backEnd.light->scissorWidth, backEnd.light->scissorHeight);

		// Set the depth bounds
		if (glConfig.depthBoundsTest)
			GL_DepthBounds(backEnd.light->depthMin, backEnd.light->depthMax);

		// Clear the stencil buffer if needed
		if (backEnd.light->castShadows)
		{
			GL_StencilMask(255);

			qglClearStencil(128);
			qglClear(GL_STENCIL_BUFFER_BIT);
		}

		// Render selfShadow meshes to the stencil buffer
		RB_StencilShadowPass(backEnd.light->shadows[0], backEnd.light->numShadows[0]);

		// Render noSelfShadow meshes with lighting
		RB_LightInteractionPass(backEnd.light->interactions[1], backEnd.light->numInteractions[1]);

		// Render noSelfShadow meshes to the stencil buffer
		RB_StencilShadowPass(backEnd.light->shadows[1], backEnd.light->numShadows[1]);

		// Render selfShadow meshes with lighting
		RB_LightInteractionPass(backEnd.light->interactions[0], backEnd.light->numInteractions[0]);
	}

	// Restore the scissor rect
	GL_Scissor(backEnd.viewport.x, backEnd.viewport.y, backEnd.viewport.width, backEnd.viewport.height);

	// Restore the depth bounds
	if (glConfig.depthBoundsTest)
		GL_DepthBounds(0.0f, 1.0f);
}

/*
 ==================
 RB_RenderFogVolume
 ==================
*/
static void RB_RenderFogVolume ()
{
	int		cornerIndices[6][4] = {{3, 2, 6, 7}, {0, 1, 5, 4}, {2, 3, 1, 0}, {4, 5, 7, 6}, {1, 3, 7, 5}, {2, 0, 4, 6}};
	int		i;

	// Draw it
	qglBegin(GL_QUADS);
	for (i = 0; i < 6; i++){
		qglVertex3fv(backEnd.light->corners[cornerIndices[i][0]]);
		qglVertex3fv(backEnd.light->corners[cornerIndices[i][1]]);
		qglVertex3fv(backEnd.light->corners[cornerIndices[i][2]]);
		qglVertex3fv(backEnd.light->corners[cornerIndices[i][3]]);
	}
	qglEnd();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_RenderStencilFogVolume
 ==================
*/
static void RB_RenderStencilFogVolume ()
{
	// Clear the stencil buffer
	GL_StencilMask(255);

	qglClearStencil(128);
	qglClear(GL_STENCIL_BUFFER_BIT);

	// Set the GL state
	GL_LoadMatrix(GL_MODELVIEW, backEnd.viewParms.worldMatrix);

	GL_DisableTexture();

	GL_Enable(GL_CULL_FACE);

	GL_Disable(GL_POLYGON_OFFSET_FILL);

	GL_Disable(GL_ALPHA_TEST);

	GL_Disable(GL_BLEND);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_ALWAYS, 128, 255);

	GL_ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(255);

	GL_DepthRange(0.0, 1.0);

	// Draw it
	GL_CullFace(GL_BACK);
	GL_StencilOp(GL_KEEP, GL_INCR, GL_KEEP);

	RB_RenderFogVolume();

	GL_CullFace(GL_FRONT);
	GL_StencilOp(GL_KEEP, GL_DECR, GL_KEEP);

	RB_RenderFogVolume();
}

/*
 ==================
 RB_RenderFogPassInteraction
 ==================
*/
static void RB_RenderFogPassInteraction ()
{
	RB_Deform();

	RB_UpdateVertexBuffer(backEnd.vertexBuffer, backEnd.vertices, backEnd.numVertices * sizeof(vertexArray_t));

	RB_SetMaterialState();

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglVertexPointer(3, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_XYZ);

	RB_DrawElements();
}

/*
 ==================
 RB_FogPass
 ==================
*/
static void RB_FogPass (mesh_t *meshes, int numMeshes)
{
	mesh_t			 *mesh;
	uint		     sort = 0;
	material_t		 *material, *oldMaterial = NULL;
	renderEntity_t   *entity, *oldEntity = NULL;
	meshType_t		 type;
	bool			 caps;
	bool		     skip;
	vec3_t			 color;
	int				 i, j;

	if (!numMeshes)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_FogPass ----------\n");

	// Render the fog volume to the stencil buffer
	RB_RenderStencilFogVolume();

	// Set the render function
	backEnd.renderBatch = RB_RenderFogPassInteraction;

	// Set the GL state
	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_BLEND);
	GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_EQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_LESS, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(255);

	GL_SelectTexture(0);
	GL_TexEnv(GL_MODULATE);
	GL_LoadIdentity(GL_TEXTURE);

	GL_Enable(GL_TEXTURE_GEN_S);
	GL_Enable(GL_TEXTURE_GEN_T);

	GL_TexGen(GL_S, GL_OBJECT_LINEAR);
	GL_TexGen(GL_T, GL_OBJECT_LINEAR);

	GL_SelectTexture(1);
	GL_TexEnv(GL_MODULATE);
	GL_LoadIdentity(GL_TEXTURE);

	GL_Enable(GL_TEXTURE_GEN_S);
	GL_Enable(GL_TEXTURE_GEN_T);

	GL_TexGen(GL_S, GL_OBJECT_LINEAR);
	GL_TexGen(GL_T, GL_OBJECT_LINEAR);

	// Run through the light stages
	for (i = 0, backEnd.lightStage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, backEnd.lightStage++)
	{
		if (!backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->conditionRegister])
			continue;

		// Set up the color
		color[0] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[0]];
		color[1] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[1]];
		color[2] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[2]];

		qglColor3fv(color);

		// Set up the textures
		GL_SelectTexture(0);
		GL_EnableTexture(backEnd.lightStage->textureStage.texture->uploadTarget);
		GL_BindTexture(backEnd.lightStage->textureStage.texture);

		GL_SelectTexture(1);
		GL_EnableTexture(backEnd.lightMaterial->lightFalloffImage->uploadTarget);
		GL_BindTexture(backEnd.lightMaterial->lightFalloffImage);

		// Run through the meshes
		for (j = 0, mesh = meshes; j < numMeshes; j++, mesh++)
		{
			if (mesh->sort != sort)
			{
				sort = mesh->sort;

				// Render the last batch
				RB_RenderBatch();

				// Decompose sort
				R_DecomposeSort(sort, &material, &entity, &type, &caps);

				// Evaluate registers if needed
				if (material != oldMaterial || entity != oldEntity)
					R_EvaluateRegisters(material, backEnd.time, entity->materialParms, backEnd.materialParms);

				// Skip if condition evaluated to false
				if (!material->expressionRegisters[material->conditionRegister])
				{
					skip = true;
					continue;
				}

				// Set the entity state if needed
				if (entity != oldEntity)
				{
					RB_SetEntityState(entity);

					// Set the light state for this entity
					RB_SetLightState(entity, backEnd.light, backEnd.lightMaterial, backEnd.lightStage);
				}

				// Create a new batch
				RB_SetupBatch(material, entity, false, false);

				oldMaterial = material;
				oldEntity = entity;

				skip = false;
			}

			if (skip)
				continue;

			// Batch the mesh
			RB_BatchMesh(type, mesh->data);
		}

		// Render the last batch
		RB_RenderBatch();
	}

	// Set the GL state
	GL_Enable(GL_CULL_FACE);
	GL_CullFace(GL_BACK);

	GL_Disable(GL_POLYGON_OFFSET_FILL);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_LEQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_ALWAYS, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	// Set the entity state
	RB_SetEntityState(rg.worldEntity);

	// Run through the light stages
	for (i = 0, backEnd.lightStage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, backEnd.lightStage++)
	{
		if (!backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->conditionRegister])
			continue;

		// Set up the color
		color[0] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[0]];
		color[1] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[1]];
		color[2] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[2]];

		qglColor3fv(color);

		// Set up the textures
		GL_SelectTexture(0);
		GL_EnableTexture(backEnd.lightStage->textureStage.texture->uploadTarget);
		GL_BindTexture(backEnd.lightStage->textureStage.texture);

		GL_SelectTexture(1);
		GL_EnableTexture(backEnd.lightMaterial->lightFalloffImage->uploadTarget);
		GL_BindTexture(backEnd.lightMaterial->lightFalloffImage);

		// Set the light state for this entity
		RB_SetLightState(rg.worldEntity, backEnd.light, backEnd.lightMaterial, backEnd.lightStage);

		// Render the fog volume
		RB_RenderFogVolume();
	}

	// Restore the GL state
	GL_SelectTexture(1);
	GL_DisableTexture();

	GL_Disable(GL_TEXTURE_GEN_S);
	GL_Disable(GL_TEXTURE_GEN_T);

	GL_SelectTexture(0);

	GL_Disable(GL_TEXTURE_GEN_S);
	GL_Disable(GL_TEXTURE_GEN_T);
}

/*
 ==================
 RB_RenderBlendLightInteraction
 ==================
*/
static void RB_RenderBlendLightInteraction ()
{
	RB_Deform();

	RB_UpdateVertexBuffer(backEnd.vertexBuffer, backEnd.vertices, backEnd.numVertices * sizeof(vertexArray_t));

	RB_SetMaterialState();

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglVertexPointer(3, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_XYZ);

	RB_DrawElements();
}

/*
 ==================
 RB_BlendLight
 ==================
*/
static void RB_BlendLight (mesh_t *meshes, int numMeshes)
{
	mesh_t			 *mesh;
	uint		     sort = 0;
	material_t		 *material, *oldMaterial = NULL;
	renderEntity_t   *entity, *oldEntity = NULL;
	meshType_t		 type;
	bool			 caps;
	bool			 skip;
	vec4_t			 color;
	int				 i, j;

	if (!numMeshes)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_BlendLight ----------\n");

	// Set the render function
	backEnd.renderBatch = RB_RenderBlendLightInteraction;

	// Set the GL state
	GL_Disable(GL_ALPHA_TEST);

	GL_Enable(GL_DEPTH_TEST);
	GL_DepthFunc(GL_EQUAL);

	GL_Enable(GL_STENCIL_TEST);
	GL_StencilFunc(GL_ALWAYS, 128, 255);
	GL_StencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(255);

	GL_SelectTexture(0);
	GL_TexEnv(GL_MODULATE);
	GL_LoadIdentity(GL_TEXTURE);

	GL_Enable(GL_TEXTURE_GEN_S);
	GL_Enable(GL_TEXTURE_GEN_T);
	GL_Enable(GL_TEXTURE_GEN_Q);

	GL_TexGen(GL_S, GL_OBJECT_LINEAR);
	GL_TexGen(GL_T, GL_OBJECT_LINEAR);
	GL_TexGen(GL_Q, GL_OBJECT_LINEAR);

	GL_SelectTexture(1);
	GL_TexEnv(GL_MODULATE);
	GL_LoadIdentity(GL_TEXTURE);

	qglTexCoord2f(0.0, 0.5);

	GL_Enable(GL_TEXTURE_GEN_S);

	GL_TexGen(GL_S, GL_OBJECT_LINEAR);

	// Run through the light stages
	for (i = 0, backEnd.lightStage = backEnd.lightMaterial->stages; i < backEnd.lightMaterial->numStages; i++, backEnd.lightStage++)
	{
		if (!backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->conditionRegister])
			continue;

		// Set the GL state
		if (backEnd.lightStage->blend)
		{
			if (backEnd.lightStage->blendSrc == GL_ZERO && backEnd.lightStage->blendDst == GL_ONE)
				continue;

			GL_Enable(GL_BLEND);
			GL_BlendFunc(backEnd.lightStage->blendSrc, backEnd.lightStage->blendDst);
		}
		else
			GL_Disable(GL_BLEND);

		// Set up the color
		color[0] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[0]];
		color[1] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[1]];
		color[2] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[2]];
		color[3] = backEnd.lightMaterial->expressionRegisters[backEnd.lightStage->colorStage.registers[3]];

		qglColor4fv(color);

		// Set up the textures
		GL_SelectTexture(0);
		GL_EnableTexture(backEnd.lightStage->textureStage.texture->uploadTarget);
		GL_BindTexture(backEnd.lightStage->textureStage.texture);

		GL_SelectTexture(1);
		GL_EnableTexture(backEnd.lightMaterial->lightFalloffImage->uploadTarget);
		GL_BindTexture(backEnd.lightMaterial->lightFalloffImage);

		// Run through the meshes
		for (j = 0, mesh = meshes; j < numMeshes; j++, mesh++)
		{
			if (mesh->sort != sort)
			{
				sort = mesh->sort;

				// Render the last batch
				RB_RenderBatch();

				// Decompose sort
				R_DecomposeSort(sort, &material, &entity, &type, &caps);

				// Evaluate registers if needed
				if (material != oldMaterial || entity != oldEntity)
					R_EvaluateRegisters(material, backEnd.time, entity->materialParms, backEnd.materialParms);

				// Skip if condition evaluated to false
				if (!material->expressionRegisters[material->conditionRegister])
				{
					skip = true;
					continue;
				}

				// Set the entity state if needed
				if (entity != oldEntity)
				{
					RB_SetEntityState(entity);

					// Set the light state for this entity
					RB_SetLightState(entity, backEnd.light, backEnd.lightMaterial, backEnd.lightStage);
				}

				// Create a new batch
				RB_SetupBatch(material, entity, false, false);

				oldMaterial = material;
				oldEntity = entity;

				skip = false;
			}

			if (skip)
				continue;

			// Batch the mesh
			RB_BatchMesh(type, mesh->data);
		}

		// Render the last batch
		RB_RenderBatch();
	}

	// Restore the GL state
	GL_SelectTexture(1);
	GL_DisableTexture();

	GL_Disable(GL_TEXTURE_GEN_S);

	GL_SelectTexture(0);

	GL_Disable(GL_TEXTURE_GEN_S);
	GL_Disable(GL_TEXTURE_GEN_T);
	GL_Disable(GL_TEXTURE_GEN_Q);
}

/*
 ==================
 RB_FogAllLights
 ==================
*/
void RB_FogAllLights (light_t *lights, int numLights)
{
	int		i;

	if (!numLights)
		return;

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_FogAllLights ----------\n");

	// Run through the lights
	for (i = 0, backEnd.light = lights; i < numLights; i++, backEnd.light++)
	{
		// Set the light material
		backEnd.lightMaterial = backEnd.light->material;

		// Evaluate registers
		R_EvaluateRegisters(backEnd.lightMaterial, backEnd.time, backEnd.light->materialParms, backEnd.materialParms);

		// Skip if condition evaluated to false
		if (!backEnd.lightMaterial->expressionRegisters[backEnd.lightMaterial->conditionRegister])
			continue;

		// Set the scissor rect
		GL_Scissor(backEnd.light->scissorX, backEnd.light->scissorY, backEnd.light->scissorWidth, backEnd.light->scissorHeight);

		// Set the depth bounds
		if (glConfig.depthBoundsTest)
			GL_DepthBounds(backEnd.light->depthMin, backEnd.light->depthMax);

		// Render meshes with lighting
		if (backEnd.lightMaterial->fogLight)
			RB_FogPass(backEnd.light->interactions[0], backEnd.light->numInteractions[0]);
		else
			RB_BlendLight(backEnd.light->interactions[0], backEnd.light->numInteractions[0]);
	}

	// Restore the scissor rect
	GL_Scissor(backEnd.viewport.x, backEnd.viewport.y, backEnd.viewport.width, backEnd.viewport.height);

	// Restore the depth bounds
	if (glConfig.depthBoundsTest)
		GL_DepthBounds(0.0f, 1.0f);
}


/*
 ==============================================================================

    2D RENDERING

 ==============================================================================
*/


/*
 ==================
 RB_RenderOverlayMaterial
 ==================
*/
void RB_RenderOverlayMaterial ()
{
	stage_t		*stage;
	int			i;

	if (r_logFile->integerValue)
		QGL_LogPrintf("----- RB_RenderOverlayMaterial ( %s ) -----\n", backEnd.material->name);

	// Capture current render if needed
	if (backEnd.material->sort == SORT_POST_PROCESS)
		RB_CaptureCurrentRender(0, rg.currentRenderTexture);

	RB_Deform();

	RB_UpdateVertexBuffer(backEnd.vertexBuffer, backEnd.vertices, backEnd.numVertices * sizeof(vertexArray_t));

	RB_SetMaterialState();

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglVertexPointer(3, GL_FLOAT, sizeof(vertexArray_t), backEnd.vertexBuffer->pointer + BUFFER_OFFSET_XYZ);

	for (i = 0, stage = backEnd.material->stages; i < backEnd.material->numStages; i++, stage++)
	{
		if (!backEnd.material->expressionRegisters[stage->conditionRegister])
			continue;

		if (stage->blend)
		{
			if (stage->blendSrc == GL_ZERO && stage->blendDst == GL_ONE)
				continue;
		}

		RB_SetStageState(stage);

		if (stage->shaders)
		{
			RB_SetupShaderStage(&stage->shaderStage);

			RB_DrawElements();

			RB_CleanupShaderStage(&stage->shaderStage);
		}
		else
		{
			RB_SetupTextureStage(&stage->textureStage);
			RB_SetupColorStage(&stage->colorStage);

			RB_DrawElements();

			RB_CleanupColorStage(&stage->colorStage);
			RB_CleanupTextureStage(&stage->textureStage);
		}
	}
}